#include "simple/support/meta/find.hpp"
#include "simple/support/meta/bind.hpp"
#include "simple/support/meta/list.hpp"

#include <type_traits>

using namespace simple::support;


template <size_t i>
using size_constant = std::integral_constant<size_t, i>;

// find double counting floats on the way
template <typename count, typename T>
struct count_float_until_double
{
	static constexpr bool value = std::is_same_v<T, double>;
	using binding = size_constant<count{} + std::is_same_v<T,float>>;
};

// find double counting floats on the way
template <typename T, typename count>
struct count_float_until_double2
{
	static constexpr bool value = std::is_same_v<T, double>;
	using binding = size_constant<count{} + std::is_same_v<T,float>>;
};

// find a type in hierarchy
template <typename index, typename T>
struct deep_find_operator;
template <typename T, std::size_t j, std::size_t ... i>
struct deep_find_operator<std::index_sequence<j, i...>, T>
{
	static constexpr bool value = std::is_same_v<T, std::nullptr_t>;
	using binding = std::conditional_t<value,
		std::index_sequence<j, i...>,
		std::index_sequence<j+1, i...>
	>;
};
template <std::size_t j, std::size_t ... i, typename ... Ts>
struct deep_find_operator<std::index_sequence<j, i...>, meta::list<Ts...>>
{
	using subfound = meta::find_t<meta::bind<deep_find_operator, std::index_sequence<0, j, i...>>, meta::list<Ts...>>;
	static constexpr bool value = (subfound::value != sizeof...(Ts));
	using binding = std::conditional_t<value,
		typename subfound::functor::binding,
		std::index_sequence<j+1, i...>
	>;
};

int main()
{
	static_assert(meta::find_v<bool,
		meta::list<char, int, bool, double>>
		== 2
	);

	static_assert(meta::find_v<short,
		meta::list<char, int, bool, double>>
		== 4
	);

	static_assert(meta::find_if_v<std::is_floating_point,
		meta::list<char, int, bool, double>>
		== 3
	);

	static_assert(meta::find_if_v<std::is_array,
		meta::list<char, int, bool, double>>
		== 4
	);

	static_assert(std::is_same_v<
		meta::find_if_t<std::is_floating_point,
			meta::list<char, bool, double, int>>,
		double
	>);

	using found = meta::find_t
	<
		meta::bind<count_float_until_double, size_constant<0>>,
		meta::list<char, float, bool, float, double, int, float>
	>;

	// found double
	static_assert(std::is_same_v<found::type, double>);
	// at index 4
	static_assert(found::value == 4);
	// counted 2 floats
	static_assert(found::functor::binding{} == 2);

	using found2 = meta::find_t
	<
		meta::bind_after<count_float_until_double2, size_constant<0>>,
		meta::list<char, float, bool, float, double, int, float>
	>;

	// found double
	static_assert(std::is_same_v<found2::type, double>);
	// at index 4
	static_assert(found2::value == 4);
	// counted 2 floats
	static_assert(found2::functor::binding{} == 2);

	using deep_found = meta::find_t
	<
		meta::bind<deep_find_operator, std::index_sequence<0>>,
		meta::list<char, float, std::nullptr_t, float, double, int, float>
	>;
	static_assert(deep_found::value == 2);
	static_assert(std::is_same_v<deep_found::functor::binding, std::index_sequence<2>>);

	using deep_found2 = meta::find_t
	<
		meta::bind<deep_find_operator, std::index_sequence<0>>,
		meta::list<
			char, float,
			meta::list<int, std::nullptr_t, int, int>,
			float, double, int, float
		>
	>;
	static_assert(deep_found2::value == 2);
	static_assert(std::is_same_v<deep_found2::functor::binding, std::index_sequence<1,2>>);

	using deep_found3 = meta::find_t
	<
		meta::bind<deep_find_operator, std::index_sequence<0>>,
		meta::list<
			char, float,
			meta::list<
				int,
				meta::list<char, char, char, std::nullptr_t>,
				int, int
			>,
			float, double, int, float
		>
	>;
	static_assert(deep_found3::value == 2);
	static_assert(std::is_same_v<deep_found3::functor::binding, std::index_sequence<3,1,2>>);

	return 0;
}
